迁移指南
了解在升级 Rive Flutter 运行时主要版本时如何迁移你的 Flutter 应用,包括破坏性变更和新功能。
版本 0.14.0
这是 Rive Flutter 的一次重大更新。我们完全移除了之前用于 Rive 运行时的所有 Dart 代码,并用我们的底层 C++ 运行时取而代之。请参见 Flutter 版 Rive Native 页面了解更多详情。
这导致了底层 API 的许多变更,之前可通过 Dart 访问的大部分代码库现在通过 FFI 在 C++ 中实现。
0.14.0 的新变化
此版本的 Rive Flutter 增加了对以下内容的支持:
- Rive 渲染器
- 数据绑定
- 布局
- 滚动
- N 切片
- 矢量羽化
- 所有其他之前未在旧版 Rive Flutter 中实现的 Rive 新增功能
- 包含 Rive C++ 运行时的最新修复和改进
- 添加预构建库,并支持手动构建。更多信息请参见 rive_native 包
- 移除
rive_common包,替换为rive_native
现在 Rive Flutter 使用了核心 Rive C++ 运行时,你可以期待新的 Rive 功能更快地在 Rive Flutter 中得到支持。
你所有的 Rive 图形仍然会像以前一样显示和运行。
要求
Dart 和 Flutter 版本
此版本将版本要求提升至:
sdk: ">=3.5.0 <4.0.0"
flutter: ">=3.3.0"
必需的设置
重要提示: 你必须在应用启动时或使用 Rive 之前调用 RiveNative.init。例如,在 main.dart 中:
import 'package:rive/rive.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await RiveNative.init(); // 在使用 Rive 之前调用 init
runApp(const MyApp());
}
迁移指南
快速迁移检查清单
- ✅ 更新
pubspec.yaml依赖为0.14.0或更高版本dependencies:
rive: ^0.14.0-dev.8 # 或最新版本 - ✅ 在
main()函数中添加RiveNative.init(),或在使用 Rive 之前调用。 - ✅ 将
Rive和RiveAnimation组件替换为RiveWidget或RiveWidgetBuilder - ✅ 更新控制器以使用新的 API,参见
RiveWidgetController - ✅ 审查并更新任何自定义资源加载代码
- ✅ 测试你的图形和交互
已移除的类
以下类已完全移除:
Rive和RiveAnimation组件 → 使用RiveWidget和RiveWidgetBuilderRiveAnimationController及其子类 → 使用RiveWidgetController、SingleAnimationPainter和StateMachinePainterOneShotAnimation和SimpleAnimation→ 使用SingleAnimationPainter播放单个动画StateMachineController→ 改用StateMachine(可通过RiveWidgetController.stateMachine访问)RiveEvent→ 替换为EventSMITrigger→ 替换为TriggerInputSMIBool→ 替换为BooleanInputSMINumber→ 替换为NumberInputFileAssetLoader→ 替换为创建File时的可选回调
加载 Rive 文件
RiveFile 已移除,替换为 File。重要变更:
新 API
final file = await File.decode(bytes, factory: Factory.rive);
final artboard = file.defaultArtboard();
final artboard = file.artboard('MyArtboard');
旧 API
final file = await RiveFile.import(bytes);
final artboard = file.mainArtboard;
final artboard = file.artboardByName('MyArtboard');
提 供的 Factory 决定了将使用的渲染器。使用 Factory.rive 使用 Rive 渲染器,或使用 Factory.flutter 使用内置的 Flutter 渲染器(Skia 或 Impeller)。
⚠️ 警告:矢量羽化仅适用于 Rive 渲染器。
关键变更:
- 创建 Rive File 现在需要工厂参数(
Factory.rive或Factory.flutter) - 将
RiveFile.import替换为File.decode(),返回Future<File> - 将
mainArtboard替换为defaultArtboard() - 将
artboardByName(name)替换为artboard(name) - 将
RiveFile.network替换为File.url - 将
RiveFile.file替换为File.path
组件迁移
参见更新的示例应用以获取完整的迁移指南,包括如何使用新的 RiveWidget 和 RiveWidgetBuilder API。
| 旧组件 | 新组件 | 备注 |
|---|---|---|
Rive/RiveAnimation | RiveWidget/RiveWidgetBuilder | 直接替换 |
新 API - 选项 1
class SimpleAssetAnimation extends StatefulWidget {
const SimpleAssetAnimation({Key? key}) : super(key: key);
@override
State<SimpleAssetAnimation> createState() => _SimpleAssetAnimationState();
}
class _SimpleAssetAnimationState extends State<SimpleAssetAnimation> {
late final fileLoader = FileLoader.fromAsset(
'assets/off_road_car.riv',
riveFactory: Factory.rive,
);
@override
void dispose() {
fileLoader.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Animation'),
),
body: Center(
child: RiveWidgetBuilder(
fileLoader: fileLoader,
builder: (context, state) => switch (state) {
RiveLoading() => const CircularProgressIndicator(),
RiveFailed() => Text('Failed to load: ${state.error}'),
RiveLoaded() => RiveWidget(
controller: state.controller,
fit: Fit.cover,
),
},
),
),
);
}
}
新 API - 选项 2
class SimpleAssetAnimation extends StatefulWidget {
const SimpleAssetAnimation({Key? key}) : super(key: key);
@override
State<SimpleAssetAnimation> createState() => _SimpleAssetAnimationState();
}
class _SimpleAssetAnimationState extends State<SimpleAssetAnimation> {
File? file;
RiveWidgetController? controller;
bool isInitialized = false;
@override
void initState() {
super.initState();
initRive();
}
void initRive() async {
file = (await File.asset('assets/off_road_car.riv', riveFactory: Factory.rive))!;
controller = RiveWidgetController(file!);
setState(() => isInitialized = true);
}
@override
void dispose() {
controller?.dispose();
file?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Animation'),
),
body: Center(
child: isInitialized && controller != null
? RiveWidget(
controller: controller!,
fit: Fit.cover,
)
: const CircularProgressIndicator(),
),
);
}
}
旧 API
class SimpleAssetAnimation extends StatelessWidget {
const SimpleAssetAnimation({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Animation'),
),
body: const Center(
child: RiveAnimation.asset(
'assets/off_road_car.riv',
fit: BoxFit.cover,
),
),
);
}
}
控制器迁移
| 旧控制器 | 新控制器 | 备注 |
|---|---|---|
RiveAnimationController | RiveWidgetController | 组件的主控制器 |
StateMachineController | StateMachine | 直接访问状态机 |
OneShotAnimation 和 SimpleAnimation | SingleAnimationPainter | 用于单个动画 |
使用新的 RiveWidgetController 的示例:
final file = await File.asset('assets/off_road_car.riv', riveFactory: Factory.rive);
final controller = RiveWidgetController(file!);
final artboard = controller.artboard; // 访问已加载的画板
final viewModelInstance = controller.dataBind(DataBind.auto()); // 自动数据绑定
可选择指定要使用的画板和状态机:
final file = await File.asset('assets/off_road_car.riv', riveFactory: Factory.rive);
final controller = RiveWidgetController(
file,
artboardSelector: ArtboardSelector.byName('Main'),
stateMachineSelector: StateMachineSelector.byName('State Machine 1'),
);
播放动画
⚠️ 警告:此功能已弃用。我们强烈建议通过状态机来播放和混合动画。
在之前的版本中,你可以通过向 RiveAnimation 传递 animations: ['myAnimation'] 来直接播放动画。
要在新版本中实现相同的效果,请使用 SingleAnimationPainter 和 RiveArtboardWidget 代替 RiveWidgetController 和 RiveWidget。
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
/// 这是一个替代控制器(绘制器),用于代替 [RiveWidgetController]。
///
/// 此绘制器用于绘制/推进状态机。功能上与 [RiveWidgetController] 非常相似,
/// 我们推荐大多数用例使用 [RiveWidgetController]。
class ExampleSingleAnimationPainter extends StatefulWidget {
const ExampleSingleAnimationPainter({super.key});
@override
State<ExampleSingleAnimationPainter> createState() =>
_ExampleSingleAnimationPainterState();
}
class _ExampleSingleAnimationPainterState
extends State<ExampleSingleAnimationPainter> {
late File file;
Artboard? artboard;
late SingleAnimationPainter painter;
@override
void initState() {
super.initState();
init();
}
void init() async {
file = (await File.asset(
'assets/off_road_car.riv',
riveFactory: RiveExampleApp.getCurrentFactory,
))!;
painter = SingleAnimationPainter('idle');
artboard = file.defaultArtboard();
setState(() {});
}
@override
void dispose() {
painter.dispose();
artboard?.dispose();
file.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (artboard == null) {
return const Center(child: CircularProgressIndicator());
}
return RiveArtboardWidget(
artboard: artboard!,
painter: painter,
);
}
}
要播放和混合多个动画,你需要创建自己的绘制器。参见
SingleAnimationPainter的实现并扩展它来创建和推进多个动画。
处理状态机输入
对于更高级的用例,建议考虑使用数据绑定
StateMachineController 已移除,替换为 StateMachine。重要变更:
状态机输入:新 API
stateMachine.trigger('myTrigger');
stateMachine.boolean('myBool');
stateMachine.number('myNumber');
状态机输入:旧 API
controller.getTriggerInput('myTrigger');
controller.getBooleanInput('myBool');
controller.getNumberInput('myNumber');
你可以从 RiveWidgetController 访问 stateMachine:
final controller = RiveWidgetController(file);
final stateMachine = controller.stateMachine;
建议在不再需要时手动释放输入:
input.dispose()
嵌套输入
你可以通过提供可选的 path 参数访问嵌套输入:
stateMachine.boolean('myBool', path: 'nested/path');
stateMachine.number('myNumber', path: 'nested/path');
stateMachine.trigger('myTrigger', path: 'nested/path');